﻿using System;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Threading;
using System.Windows.Controls;
using System.ComponentModel;
using ModelViewer.OnScreenMessages;
using System.Collections.Generic;


namespace ModelViewer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        ModelViewControl modelControl;
        System.Windows.Forms.OpenFileDialog fileDialog;
        System.Timers.Timer timer;

        ParameterizedThreadStart loadingThread;
        IAsyncResult loadingResult;

        ContentBuilder contentBuilder;
        ContentManager contentManager;

        Model model;

        Info infoWindow;

        string buildError;

        List<FileInfo> sampleModels;

        public MainWindow ()
        {
            InitializeComponent();

            timer = new System.Timers.Timer(30); // 30 milliseconds is roughly 30 fps
            timer.Elapsed += OnUpdate;
            timer.Start();

            // set the field of view slider range
            sldFov.Minimum = ArcBallCamera.min_fov;
            sldFov.Maximum = ArcBallCamera.max_fov;
            sldFov.Value = Math.PI / 3;
            lblFov.Content = 60;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Create the info window and set owner so it closes when main window closes
            infoWindow = new Info();
            infoWindow.Owner = this;

            modelControl = new ModelViewControl();
            modelControl.ModelViewBorder = modelControlBorder;
            modelControl.SizeChanged += new EventHandler(modelControl_SizeChanged);
            modelControl.MouseClick += new System.Windows.Forms.MouseEventHandler(modelControl_MouseClick);
            modelControl.AmbientPower(0.5f);
            sldAmbient.Value = 0.5f;

            contentBuilder = new ContentBuilder();
            contentManager = new ContentManager(modelControl.Services, contentBuilder.OutputDirectory);

            windowsFormsHost1.Child = modelControl;

            // sets the errorsTextBox in the ModelViewControl.cs file 
            modelControl.ErrorTextBox = textBoxErrors;

            //modelControl.Focus();

            Microsoft.Xna.Framework.Input.Mouse.WindowHandle = windowsFormsHost1.Handle;

            fileDialog = new System.Windows.Forms.OpenFileDialog();
            string assemblyLocation = Assembly.GetExecutingAssembly().Location;
            string relativePath = Path.Combine(assemblyLocation, "../SampleContent");
            string contentPath = Path.GetFullPath(relativePath);

            fileDialog.InitialDirectory = contentPath;

            fileDialog.Title = "Load Model";

            fileDialog.Filter = "Model Files (*.fbx;*.x)|*.fbx;*.x|" +
                                "FBX Files (*.fbx)|*.fbx|" +
                                "X Files (*.x)|*.x|" +
                                "All Files (*.*)|*.*";


            // Default sample file to load.
            string fileName = contentPath + @"\Kahuna\Kahuna.fbx" + " ...";

            // Override default filename if a model was dragged onto .exe to launch application.
            string dragAndDroppedFilePath = Utils.FullFilePathOfDragAndDroppedFile(App.StartupArgs);
            if (dragAndDroppedFilePath != String.Empty)
            {
                fileName = dragAndDroppedFilePath;
            }

            // Loading indicator.
            statusBar.Items[0] = "Loading " + fileName;

            // Use a new thread to load the model.
            loadingThread = new ParameterizedThreadStart(LoadModel);
            loadingResult = loadingThread.BeginInvoke(fileName, OnLoadComplete, null);

            PopulateSampleModels(contentPath);
        }

        void PopulateSampleModels(string folder)
        {
            DirectoryInfo di = new DirectoryInfo(folder);
            sampleModels = new List<FileInfo>(di.GetFiles("*.fbx", SearchOption.AllDirectories));
            sampleModels.AddRange(di.GetFiles("*.x", SearchOption.AllDirectories));
            LoadSampleModelMenuItem.Items.Clear();

            foreach (FileInfo fi in sampleModels) {
                MenuItem mi = new MenuItem();
                mi.Header = fi.Name;
                mi.Click += new RoutedEventHandler(mi_Click);
                LoadSampleModelMenuItem.Items.Add(mi);
            }
        }

        void mi_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = (MenuItem)sender;
            LoadModel(sampleModels.Find(x => x.Name == mi.Header).FullName);
        }
                    
        void modelControl_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (!modelControl.Focused)
                modelControl.Focus(); 
        }

        void modelControl_SizeChanged(object sender, EventArgs e)
        {
            ModelViewControl.ScreenSize = new Microsoft.Xna.Framework.Vector2(modelControl.Width, modelControl.Height);
            
            // set the Border size to fit around the ModelViewControl
            modelControlBorder.Height = modelControl.Height + 6;
            modelControlBorder.Width = modelControl.Width + 6;
        }

        private void OnUpdate(object sender, EventArgs e)
        {
            if (modelControl != null)
                modelControl.Invalidate();
        }

        /// <summary>
        /// Load a model from file and associate it with our model viewer
        /// </summary>
        private void LoadModel(object filename)
        {
            try
            {
                modelControl.BeginLoading();

                // Unload any existing model.
                modelControl.LoadModel(null);
                contentManager.Unload();

                // Tell the ContentBuilder what to build.
                contentBuilder.Clear();
                contentBuilder.Add((string)filename, "Model", null, "ModelProcessor");

                // Build this new model data.
                buildError = contentBuilder.Build();

                if (string.IsNullOrEmpty(buildError))
                {
                    // This resets the textBox textBoxErrors.
                    // Must use dispatcher.  
                    Dispatcher.BeginInvoke(new Action(delegate
                    {
                        textBoxErrors.Text = "";
                    }));

                    // If the build succeeded, use the ContentManager to
                    // load the temporary .xnb file that we just created.
                    model = contentManager.Load<Model>("Model");
                    PopulateMeshNames(model);
                    modelControl.LoadModel(model);
                }
               else
                {
                    // If the build failed, display an error message in textBoxErrors.
                    // Must use Dispatcher because it is loaded on a separate thread.
                    // This specifically catches a missing asset error.
                    Dispatcher.BeginInvoke(new Action(delegate
                    {
                        textBoxErrors.Text = buildError;
                    }));
                }
            }
            catch (Exception e)
            {
                // this catches other errors
                Dispatcher.BeginInvoke(new Action(delegate
                {
                    textBoxErrors.Text = e.Message;
                }));
            }
        }

        private void OnLoadComplete(IAsyncResult result)
        {
            MainGrid.Dispatcher.Invoke(new Action(delegate()
            {
                // Refresh the info window 
                infoWindow.Setup(model, modelControl.boundingSphere);

                statusBar.Items[0] = "Ready";
                modelControl.Focus();
            }));
            loadingThread = null; // reset loading thread
            modelControl.EndLoading();
        }

        private void btnOpen_Click(object sender, RoutedEventArgs e)
        {
            if (fileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                // end the loading thread
                if (loadingThread != null)
                {
                    loadingThread.EndInvoke(loadingResult);
                    loadingThread = null;
                }

                statusBar.Items[0] = "Loading " + fileDialog.FileName + " ...";
                
                // use a new thread to load the model
                loadingThread = new ParameterizedThreadStart(LoadModel);
                loadingResult = loadingThread.BeginInvoke(fileDialog.FileName, OnLoadComplete, null);
            }
        }

        private void btnExit_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void sldAmbient_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (modelControl != null)
            {
                modelControl.AmbientPower((float)e.NewValue);
                lblAmbient.Content = e.NewValue.ToString("F2");
            }
        }

        private void sldFov_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (modelControl != null)
            {
                modelControl.SetFov((float)e.NewValue);
                int degrees = (int)Microsoft.Xna.Framework.MathHelper.ToDegrees((float)e.NewValue);
                lblFov.Content = degrees;
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            // remove our window handle reference before closing
            Microsoft.Xna.Framework.Input.Mouse.WindowHandle = System.IntPtr.Zero;
        }

        private void btnResetView_Click(object sender, RoutedEventArgs e)
        {
            modelControl.ResetView();
        }

        private void WireframeMenuItem_Click(object sender, RoutedEventArgs e)
        {
            modelControl.ShadingType = WireframeMenuItem.IsChecked ? ShadingType.Wireframe : ShadingType.Solid;
        }

        private void FPSMenuItem_Click(object sender, RoutedEventArgs e)
        {
            Message.Instance.ShowFPS = FPSMenuItem.IsChecked;
        }

        private void PopulateMeshNames(Model model)
        {
            SelectMeshMenu.Dispatcher.Invoke(new Action(delegate()
                {
                    SelectMeshMenu.Items.Clear();
                    if (model != null)
                    {
                        foreach (ModelMesh mesh in model.Meshes)
                        {
                            MenuItem mi = new MenuItem();
                            mi.Header = mesh.Name;
                            mi.IsCheckable = true;
                            mi.Click += new RoutedEventHandler(MeshMenuItem_Click);
                            SelectMeshMenu.Items.Add(mi);
                        }
                        SortDescription sd = new SortDescription("Header", ListSortDirection.Ascending);
                        SelectMeshMenu.Items.SortDescriptions.Add(sd);
                        SelectMeshMenu.Items.Refresh();

                        // Insert none at the top of the menu
                        MenuItem noneItem = new MenuItem();
                        noneItem.Header = "None";
                        noneItem.IsCheckable = true;
                        noneItem.Click += new RoutedEventHandler(MeshMenuItem_Click);
                        SelectMeshMenu.Items.Insert(0, noneItem);
                    }
                }
            ));            
        }

        void MeshMenuItem_Click(object sender, RoutedEventArgs e)
        {
            foreach (MenuItem mi in SelectMeshMenu.Items)
            {
                if (mi != sender)
                    mi.IsChecked = false;
                else
                {
                    if (mi.Header as string == "None")
                        modelControl.SetSelectedMesh(null);
                    else if (mi.IsChecked) 
                        modelControl.SetSelectedMesh(mi.Header as string);
                    else
                        modelControl.SetSelectedMesh(null);
                }

            }
        }

        private void ZoomExtentsButton_Click(object sender, RoutedEventArgs e)
        {
            modelControl.ZoomToModelExtents();
        }

        private void BoundingBoxMenuItem_Click(object sender, RoutedEventArgs e)
        {
            BoundingBoxRenderer.Instance.Enabled = BoundingBoxMenuItem.IsChecked;
        }

        // Opens the "About Kane's Model Viewer" modal window
        private void AboutMenuItem_Click(object sender, RoutedEventArgs e)
        {
            AboutWindow about = aboutPopup.Child as AboutWindow;
            aboutPopup.Placement = System.Windows.Controls.Primitives.PlacementMode.Center;
            aboutPopup.IsOpen = true;
            aboutPopup.StaysOpen = false;
        }

        private void DivideByZeroMenuItem_Click(object sender, RoutedEventArgs e)
        {
            modelControl.ExceptionGeneratorEnabled = true;
        }

        private void btnInfo_Click(object sender, RoutedEventArgs e)
        {
            infoWindow.Show();
        }

        private void BoundingSphereMenuItem_Click(object sender, RoutedEventArgs e)
        {
            BoundingSphereRenderer.Instance.Enabled = BoundingSphereMenuItem.IsChecked;
        }

        private void LoadMissingTextureModelMenuItem_Click(object sender, RoutedEventArgs e)
        {
            string assemblyLocation = Assembly.GetExecutingAssembly().Location;
            string relativePath = Path.Combine(assemblyLocation, "../SampleContent");
            string contentPath = Path.GetFullPath(relativePath);

            // use a new thread to load the model
            loadingThread = new ParameterizedThreadStart(LoadModel);
            loadingResult = loadingThread.BeginInvoke(contentPath
                + @"\KahunaNoTextures\Kahuna.fbx", OnLoadComplete, null);
        }

        private void Window_Drop (object sender, DragEventArgs e)
        {

            if (e.Data.GetDataPresent(DataFormats.FileDrop, true))
            {
                string[] droppedFilePaths = e.Data.GetData(DataFormats.FileDrop, true) as string[];
                if (droppedFilePaths.Length > 0)
                {
                    var fileName = droppedFilePaths[0];
                    // Use a new thread to load the model.
                    loadingThread = new ParameterizedThreadStart(LoadModel);
                    loadingResult = loadingThread.BeginInvoke(fileName, OnLoadComplete, null);

                }

            }

        }
    }
}
